package bf.program;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import bf.util.MyClassLoader;

public abstract class CompiableCode implements ProgElement, Compiable {

	@Override
	public void execute(State state) {
		if(compiledMethod != null) {
			try {
				compiledMethod.invoke(null, new Object[]{state});
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			iexecute(state);
		}
	}

	public abstract void iexecute(State state);

	@Override
	public Class<CompiledProgram> getCompiled() {
		if(compiled == null) compile();
		return compiled;
	}

	private static final String CLASS_PREFIX = "bf";
	private static int classId = 0;
	public static final String BASE = "bf/program/CompiledProgram";
	Class<CompiledProgram> compiled;
	Method compiledMethod;

	public CompiableCode() {
		super();
	}

	@SuppressWarnings("unchecked")
	public void compile() {
		String name = genClassName();
//		System.out.println("compile -> " + name);
		ClassWriter cw = new ClassWriter(0);
		cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, name, null, BASE, null);

		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "execute", "(Lbf/program/State;)V", null, null);
		mv.visitCode();
		Label l0 = new Label();
		mv.visitLabel(l0);

		genCode(mv);

		mv.visitInsn(RETURN);
		Label l9 = new Label();
		mv.visitLabel(l9);
		mv.visitLocalVariable("state", "Lbf/program/State;", null, l0, l9, 0);
		mv.visitMaxs(2, 1);
		mv.visitEnd();

		cw.visitEnd();
		byte[] code = cw.toByteArray();
		compiled = MyClassLoader.load(name, code);
		try {
			compiledMethod = compiled.getMethod("execute", new Class[]{State.class});
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private String genClassName() {
		String name = CLASS_PREFIX + classId;
		classId++;
		return name;
	}

	protected void genCall(MethodVisitor mv, Class<CompiledProgram> compiled) {
		String className = Type.getType(compiled).getClassName();
//		System.out.println("genCall -> " + className);

		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKESTATIC, className, "execute", "(Lbf/program/State;)V");
	}

}